/*******************************************************************
 *
 *  ttcmap.c                                                    1.0
 *
 *    TrueType Character Mappings
 *
 *  Copyright 1996-1999 by
 *  David Turner, Robert Wilhelm, and Werner Lemberg.
 *
 *  This file is part of the FreeType project, and may only be used
 *  modified and distributed under the terms of the FreeType project
 *  license, LICENSE.TXT.  By continuing to use, modify, or distribute
 *  this file you indicate that you have read the license and
 *  understand and accept it fully.
 *
 ******************************************************************/

#include "ttobjs.h"
#include "ttdebug.h"
#include "ttfile.h"
#include "ttmemory.h"
#include "ttload.h"
#include "ttcmap.h"

/* required by the tracing mode */
#undef  TT_COMPONENT
#define TT_COMPONENT      trace_cmap

/*******************************************************************
 *
 *  Function    :  CharMap_Load
 *
 *  Description :  Loads a given charmap into memory.
 *
 *  Input  :  cmap  pointer to cmap table
 *
 *  Output :  Error code.
 *
 *  Notes  :  - Assumes the the stream is already used (opened).
 *
 *            - In case of error, releases all partially allocated
 *              tables.
 *
 ******************************************************************/

LOCAL_FUNC TT_Error CharMap_Load(PCMapTable cmap, TT_Stream input)
{
	DEFINE_LOAD_LOCALS(input);

	UShort num_SH, num_Seg, i;

	UShort u, l;

	PCMap0 cmap0;
	PCMap2 cmap2;
	PCMap4 cmap4;
	PCMap6 cmap6;

	PCMap2SubHeader cmap2sub;
	PCMap4Segment segments;

	if (cmap->loaded)
		return TT_Err_Ok;

	if (FILE_Seek(cmap->offset))
		return error;

	switch (cmap->format) {
	case 0:
		cmap0 = &cmap->c.cmap0;

		if (ALLOC(cmap0->glyphIdArray, 256L) || FILE_Read((void *) cmap0->glyphIdArray, 256L))
			goto Fail;

		break;

	case 2:
		num_SH = 0;
		cmap2 = &cmap->c.cmap2;

		/* allocate subheader keys */

		if (ALLOC_ARRAY(cmap2->subHeaderKeys, 256, UShort) || ACCESS_Frame(512L))
			goto Fail;

		for (i = 0; i < 256; i++) {
			u = GET_UShort() / 8;
			cmap2->subHeaderKeys[i] = u;

			if (num_SH < u)
				num_SH = u;
		}

		FORGET_Frame();

		/* load subheaders */

		cmap2->numGlyphId = l = ((cmap->length - 2L * (256 + 3) - num_SH * 8L) & 0xffff) / 2;

		if (ALLOC_ARRAY(cmap2->subHeaders, num_SH + 1, TCMap2SubHeader) || ACCESS_Frame((num_SH + 1) * 8L))
			goto Fail;

		cmap2sub = cmap2->subHeaders;

		for (i = 0; i <= num_SH; i++) {
			cmap2sub->firstCode = GET_UShort();
			cmap2sub->entryCount = GET_UShort();
			cmap2sub->idDelta = GET_Short();
			/* we apply the location offset immediately */
			cmap2sub->idRangeOffset = GET_UShort() - (num_SH - i) * 8 - 2;

			cmap2sub++;
		}

		FORGET_Frame();

		/* load glyph ids */

		if (ALLOC_ARRAY(cmap2->glyphIdArray, l, UShort) || ACCESS_Frame(l * 2L))
			goto Fail;

		for (i = 0; i < l; i++)
			cmap2->glyphIdArray[i] = GET_UShort();

		FORGET_Frame();
		break;

	case 4:
		cmap4 = &cmap->c.cmap4;

		/* load header */

		if (ACCESS_Frame(8L))
			goto Fail;

		cmap4->segCountX2 = GET_UShort();
		cmap4->searchRange = GET_UShort();
		cmap4->entrySelector = GET_UShort();
		cmap4->rangeShift = GET_UShort();

		num_Seg = cmap4->segCountX2 / 2;

		FORGET_Frame();

		/* load segments */

		if (ALLOC_ARRAY(cmap4->segments, num_Seg, TCMap4Segment) || ACCESS_Frame((num_Seg * 4 + 1) * 2L))
			goto Fail;

		segments = cmap4->segments;

		for (i = 0; i < num_Seg; i++)
			segments[i].endCount = GET_UShort();

		(void) GET_UShort();

		for (i = 0; i < num_Seg; i++)
			segments[i].startCount = GET_UShort();

		for (i = 0; i < num_Seg; i++)
			segments[i].idDelta = GET_Short();

		for (i = 0; i < num_Seg; i++)
			segments[i].idRangeOffset = GET_UShort();

		FORGET_Frame();

		cmap4->numGlyphId = l = ((cmap->length - (16L + 8L * num_Seg)) & 0xffff) / 2;

		/* load ids */

		if (ALLOC_ARRAY(cmap4->glyphIdArray, l, UShort) || ACCESS_Frame(l * 2L))
			goto Fail;

		for (i = 0; i < l; i++)
			cmap4->glyphIdArray[i] = GET_UShort();

		FORGET_Frame();
		break;

	case 6:
		cmap6 = &cmap->c.cmap6;

		if (ACCESS_Frame(4L))
			goto Fail;

		cmap6->firstCode = GET_UShort();
		cmap6->entryCount = GET_UShort();

		FORGET_Frame();

		l = cmap6->entryCount;

		if (ALLOC_ARRAY(cmap6->glyphIdArray, cmap6->entryCount, Short) || ACCESS_Frame(l * 2L))
			goto Fail;

		for (i = 0; i < l; i++)
			cmap6->glyphIdArray[i] = GET_UShort();

		FORGET_Frame();
		break;

	default:	/* corrupt character mapping table */
		return TT_Err_Invalid_CharMap_Format;

	}
	return TT_Err_Ok;

  Fail:
	CharMap_Free(cmap);
	return error;
}

/*******************************************************************
 *
 *  Function    :  CharMap_Free
 *
 *  Description :  Releases a given charmap table.
 *
 *  Input  :  cmap   pointer to cmap table
 *
 *  Output :  Error code.
 *
 ******************************************************************/

LOCAL_FUNC TT_Error CharMap_Free(PCMapTable cmap)
{
	if (!cmap)
		return TT_Err_Ok;

	switch (cmap->format) {
	case 0:
		FREE(cmap->c.cmap0.glyphIdArray);
		break;

	case 2:
		FREE(cmap->c.cmap2.subHeaderKeys);
		FREE(cmap->c.cmap2.subHeaders);
		FREE(cmap->c.cmap2.glyphIdArray);
		break;

	case 4:
		FREE(cmap->c.cmap4.segments);
		FREE(cmap->c.cmap4.glyphIdArray);
		cmap->c.cmap4.segCountX2 = 0;
		break;

	case 6:
		FREE(cmap->c.cmap6.glyphIdArray);
		cmap->c.cmap6.entryCount = 0;
		break;

	default:
		/* invalid table format, do nothing */
		;
	}

	cmap->loaded = FALSE;
	return TT_Err_Ok;
}

/*******************************************************************
 *
 *  Function    :  CharMap_Index
 *
 *  Description :  Performs charcode->glyph index translation.
 *
 *  Input  :  cmap   pointer to cmap table
 *
 *  Output :  Glyph index, 0 in case of failure.
 *
 ******************************************************************/

static UShort code_to_index0(UShort charCode, PCMap0 cmap0);
static UShort code_to_index2(UShort charCode, PCMap2 cmap2);
static UShort code_to_index4(UShort charCode, PCMap4 cmap4);
static UShort code_to_index6(UShort charCode, PCMap6 cmap6);

LOCAL_FUNC UShort CharMap_Index(PCMapTable cmap, UShort charcode)
{
	switch (cmap->format) {
	case 0:
		return code_to_index0(charcode, &cmap->c.cmap0);
	case 2:
		return code_to_index2(charcode, &cmap->c.cmap2);
	case 4:
		return code_to_index4(charcode, &cmap->c.cmap4);
	case 6:
		return code_to_index6(charcode, &cmap->c.cmap6);
	default:
		return 0;
	}
}

/*******************************************************************
 *
 *  Function    : code_to_index0
 *
 *  Description : Converts the character code into a glyph index.
 *                Uses format 0.
 *                charCode will be masked to get a value in the range
 *                0x00-0xFF.
 *
 *  Input  :  charCode      the wanted character code
 *            cmap0         a pointer to a cmap table in format 0
 *
 *  Output :  Glyph index into the glyphs array.
 *            0 if the glyph does not exist.
 *
 ******************************************************************/

static UShort code_to_index0(UShort charCode, PCMap0 cmap0)
{
	if (charCode <= 0xFF)
		return cmap0->glyphIdArray[charCode];
	else
		return 0;
}

/*******************************************************************
 *
 *  Function    : code_to_index2
 *
 *  Description : Converts the character code into a glyph index.
 *                Uses format 2.
 *
 *  Input  :  charCode      the wanted character code
 *            cmap2         a pointer to a cmap table in format 2
 *
 *  Output :  Glyph index into the glyphs array.
 *            0 if the glyph does not exist.
 *
 ******************************************************************/

static UShort code_to_index2(UShort charCode, PCMap2 cmap2)
{
	UShort index1, idx, offset;
	TCMap2SubHeader sh2;

	index1 = cmap2->subHeaderKeys[charCode <= 0xFF ? charCode : (charCode >> 8)];

	if (index1 == 0) {
		if (charCode <= 0xFF)
			return cmap2->glyphIdArray[charCode];	/* 8bit character code */
		else
			return 0;
	} else {	/* 16bit character code */

		if (charCode <= 0xFF)
			return 0;

		sh2 = cmap2->subHeaders[index1];

		if ((charCode & 0xFF) < sh2.firstCode)
			return 0;

		if ((charCode & 0xFF) >= (sh2.firstCode + sh2.entryCount))
			return 0;

		offset = sh2.idRangeOffset / 2 + (charCode & 0xFF) - sh2.firstCode;
		if (offset < cmap2->numGlyphId)
			idx = cmap2->glyphIdArray[offset];
		else
			return 0;

		if (idx)
			return (idx + sh2.idDelta) & 0xFFFF;
		else
			return 0;
	}
}

/*******************************************************************
 *
 *  Function    : code_to_index4
 *
 *  Description : Converts the character code into a glyph index.
 *                Uses format 4.
 *
 *  Input  :  charCode      the wanted character code
 *            cmap4         a pointer to a cmap table in format 4
 *
 *  Output :  Glyph index into the glyphs array.
 *            0 if the glyph does not exist.
 *
 ******************************************************************/

static UShort code_to_index4(UShort charCode, PCMap4 cmap4)
{
	UShort index1, segCount;
	UShort i;
	TCMap4Segment seg4;

	segCount = cmap4->segCountX2 / 2;

	for (i = 0; i < segCount; i++)
		if (charCode <= cmap4->segments[i].endCount)
			break;

	/* Safety check - even though the last endCount should be 0xFFFF */
	if (i >= segCount)
		return 0;

	seg4 = cmap4->segments[i];

	if (charCode < seg4.startCount)
		return 0;

	if (seg4.idRangeOffset == 0)
		return (charCode + seg4.idDelta) & 0xFFFF;
	else {
		index1 = seg4.idRangeOffset / 2 + (charCode - seg4.startCount) - (segCount - i);

		if (index1 < cmap4->numGlyphId) {
			if (cmap4->glyphIdArray[index1] == 0)
				return 0;
			else
				return (cmap4->glyphIdArray[index1] + seg4.idDelta) & 0xFFFF;
		} else
			return 0;
	}
}

/*******************************************************************
 *
 *  Function    : code_to_index6
 *
 *  Description : Converts the character code into a glyph index.
 *                Uses format 6.
 *
 *  Input  :  charCode      the wanted character code
 *            cmap6         a pointer to a cmap table in format 6
 *
 *  Output :  Glyph index into the glyphs array.
 *            0 if the glyph does not exist (`missing character glyph').
 *
 ******************************************************************/

static UShort code_to_index6(UShort charCode, PCMap6 cmap6)
{
	UShort firstCode;

	firstCode = cmap6->firstCode;

	if (charCode < firstCode)
		return 0;

	if (charCode >= (firstCode + cmap6->entryCount))
		return 0;

	return cmap6->glyphIdArray[charCode - firstCode];
}

/* END */
